#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <readline/readline.h>
#include <readline/history.h>
#include "misc.h"
#include "gen_read.h"

int gr_open_str(GEN_READ *r, char *s)
{
  r->type = I_STRING;
  r->str = strdup(s);
  r->strp = r->str;
  r->str_end = r->str + strlen(r->str);
  r->putback_ch = -1;
  return 1;
}

int gr_open_file(GEN_READ *r, char *pth)
{
  r->type = I_FILE;
  r->file = NULL;
  r->putback_ch = -1;
  if (r->file = fopen(pth, "r")) {
    return 1;
  }
  return 0;
}

int gr_open_rdln(GEN_READ *r, char *prompt)
{
  r->type = I_READLINE;
  r->rdlnp = r->rdln = NULL;
  r->eof = 0;
  r->putback_ch = -1;
  if (prompt) {
    r->prompt = strdup(prompt);
  } else {
    r->prompt = NULL;
  }
  return 1;
}

static int blanks(char *s)
{
  while (*s) {
    if (!isspace(*s++)) {
      return 0;
    }
  }
  return 1;
}

void gr_set_prompt(GEN_READ *r, char *prompt)
{
  if (I_READLINE == r->type) {
    free(r->prompt);
    r->prompt = strdup(prompt);
  }
}

int gr_get_char(GEN_READ *r, char *c)
{
  if (r->putback_ch >= 0) {
    *c = r->putback_ch;
    r->putback_ch = -1;
    return 1;
  } else {
    switch (r->type) {
      case I_STRING:
        if (r->strp && (r->strp <= r->str_end)) {
          *c = *r->strp++;
          if (*c) {
            return 1;
          } else {
            // set *c to universal EOF char
            *c = EOF;
            return 0;
          }
        } else {
          *c = EOF;
          return 0;
        }
        break;
      case I_READLINE:
        if (r->rdln && !*r->rdlnp && r->ret_nl) {
          // Return newline on first encounter with end of string.
          *c = '\n';
          r->ret_nl = 0;
          return 1;
        } else if (!r->rdln || !*r->rdlnp) {
          // If the current line doesn't exist, or we are at the
          // terminating nul ...
          if (r->rdln) {
            free(r->rdln);
          }
          r->rdlnp = r->rdln = readline(r->prompt ? r->prompt : "");
          r->ret_nl = 1;
          if (r->rdln && *r->rdlnp && !blanks(r->rdln)) {
            // Only add lines to the history if they:
            // (1) exist.
            // (2) are nonempty and
            // (3) are not blank
            add_history(r->rdln);
          }
        }
        if (r->rdln) {
          // readline() will return NULL when EOF is encountred.
          if (*r->rdlnp) {
            // Don't go past the terminating nul if an empty line
            // ("") was returned by readline().
            *c = *r->rdlnp++;
            return 1;
          } else {
            // First time end of string seen, return newline just
            // like above.
            *c = '\n';
            r->ret_nl = 0;
            return 1;
          }
        } else {
          // r->rdln is NULL which is readline()'s way of telling us
          // that the user pressed ctrl-D.
          *c = EOF;
          r->eof = 1;
          r->ret_nl = 0;
          return 0;
        }
        break;
      case I_FILE:
        return (*c = r->file ? fgetc(r->file) : EOF) != EOF;
        break;
      default:
        return 0;
        break;
    }
  }
}

int gr_eof(GEN_READ *r)
{
  switch (r->type) {
    case I_STRING:
      return !r->strp || (r->strp > r->str_end);
      break;
    case I_READLINE:
      return !r->rdln && r->eof;
      break;
    case I_FILE:
      return r->file && feof(r->file);
      break;
    default:
      return 0;
  }
}

int gr_putback_char(GEN_READ *r, char c)
{
  r->putback_ch = c;
}

void gr_close(GEN_READ *r)
{
  r->putback_ch = -1;
  switch (r->type) {
    case I_STRING:
      if (r->str) {
        free(r->str);
      }
      r->strp = r->str = NULL;
      break;
    case I_READLINE:
      if (r->rdln) {
        free(r->rdln);
      }
      r->rdlnp = r->rdln = NULL;
      r->eof = 1;
      break;
    case I_FILE:
      if (r->file) {
        fclose(r->file);
      }
      r->file = NULL;
      break;
    default:
      break;
  }
}

#ifdef TEST_INPUT

static void init_gr(char test_type, char *arg, GEN_READ *r)
{
  switch (test_type) {
    case 'f':
      if (!gr_open_file(r, arg)) {
        fprintf(stderr, "Can't open %s\n", arg);
        exit(0);
      }
      break;
    case 's':
      if (!gr_open_str(r, arg)) {
        fprintf(stderr, "Can't initialize with string.\n");
        exit(0);
      }
      break;
    case 'r':
      if (!gr_open_rdln(r, arg)) {
        fprintf(stderr, "Can't initialize with readline.\n");
        exit(0);
      }
      break;
    default:
      fprintf(stderr, "Unrecognized input type: %c\n", test_type);
      exit(0);
      break;
  }
}

void main(int argc, char **argv)
{
  if (argc == 3 && (*argv[1] == 'f' || *argv[1] == 's' || *argv[1] == 'r')) {
    char c;
    int n = 0;
    GEN_READ r;
    init_gr(*argv[1], argv[2], &r);
    printf("Reading without testing EOF with gr_eof()\n");
    while (gr_get_char(&r, &c)) {
      printf("Char #%d, (%c) = (%d)\n", n++, c, c);
    }
    printf("Reading and testing EOF with gr_eof()\n");
    gr_close(&r);
    init_gr(*argv[1], argv[2], &r);
    n = 0;
    while (!gr_eof(&r)) {
      // Won't know that we're at eof until we actually try to get
      // another character.
      if (gr_get_char(&r, &c)) {
        printf("Char #%d, (%c) = (%d)\n", n++, c, c);
      }
    }
  } else {
    printf("args: (f <file> | s <string> | r <prompt>)\n");
  }
}
#endif
